﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Reflection;
using System.Linq;
using System.Text;
using ImpromptuInterface;

namespace RequestModel
{
	public static class Factory
	{
		public static T RequestModel<T>() where T : class
		{
			try {
				var builder = new ExpandoObject();
				var builderDic = builder as IDictionary<string, Object>;

				foreach (var property in typeof(T).GetProperties())
				{
					var attrs = property.GetCustomAttributes(typeof (FromRequestAttribute), false);
					if ( attrs.Length>0 )
					{
						var att = attrs[0] as FromRequestAttribute;

						var fieldKey = att.RequestKey ?? property.Name;
						var rqData = System.Web.HttpContext.Current.Request[fieldKey];

						if (property.PropertyType.IsArray) {
							var elemType = property.PropertyType.GetElementType();
							var elemNullableType = elemType.IsValueType ? Nullable.GetUnderlyingType(elemType) : null;

							if (string.IsNullOrEmpty(rqData)) {
								if (att.Required) {
									throw new Exception("Array field is required");
								}
								builderDic[property.Name] = null;
							}
							else {
								var items = rqData.Split(new[] { att.ArrayElementSeperator }, StringSplitOptions.None);
								var itemsArray = Array.CreateInstance(elemType, items.Length);
								var index = 0;
								foreach (var item in items) {
									var itemResult = ExtractPropertyVal(item, att.ArrayElementRequired, elemType.IsValueType, elemType, elemNullableType);
									itemsArray.SetValue(itemResult, index++);
								}

								builderDic[property.Name] = itemsArray;
							}
						}
						else {
							var nullableType = property.PropertyType.IsValueType ? Nullable.GetUnderlyingType(property.PropertyType) : null;
							builderDic[property.Name] = ExtractPropertyVal(rqData, att.Required, property.PropertyType.IsValueType, property.PropertyType, nullableType);
						}
					}
				}

				return builderDic.ActLike<T>();
			}
			catch (Exception ex) {
				return null;
			}
		}

		static object ExtractPropertyVal(string rqData, bool required, bool isValueType, Type propertyType, Type nullableType)
		{
			if (string.IsNullOrEmpty(rqData))
			{
				if (required)
				{
					throw new Exception("Field is required");
				}

				if (isValueType)
				{
					if (nullableType == null)
					{
						throw new Exception("Field is not nullable value type with no value");
					}

					return Activator.CreateInstance(propertyType);//default value of type (nullable will return null)
				}

				if (propertyType == typeof(string))
				{ //if it is empty
					return rqData;
				}
				return null;
			}

			var type = nullableType ?? propertyType;

			return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(rqData);
		}
	}
}
